#include "GameController.h"
#include "SharedConstants.h"
#include "GameConstants.h"
#include "Ground.h"
#include "WorldObject.h"
#include "Tree.h"
#include "FerrisWheel.h"
#include "BalloonsShop.h"
#include "Bench.h"
#include <cmath>

const size_t MAX_HUMAN_COUNT = 50;
const double HAPPINESS_FOR_FIRST_HUMAN = 0.1;

namespace
  {
  size_t GetHumanCountByHappiness(double i_happiness)
    {
    double coef = i_happiness - HAPPINESS_FOR_FIRST_HUMAN;
    double res = MAX_HUMAN_COUNT * std::pow(coef, 1.5);
    if (res < 0)
      return 0;
    return static_cast<size_t>(std::floor(res));
    }
  }

GameController::GameController()
  : m_money(GameConstants::STARTING_MONEY)
  , m_tree_count(0)
  , m_walkway_count(0)
  , m_water_count(0)
  , m_landscape_beauty(0.0)
  , m_ferris_wheel_count(0)
  , m_balloons_shop_count(0)
  , m_human_count(0)
  , m_bench_count(0)
  {
  }

int GameController::GetMoney() const
  {
  return m_money;
  }

void GameController::SetMoney(int i_money)
  {
  m_money = i_money;
  _GenerateAllEvents();
  }

//just setting information - no event updates is needed
void GameController::SetHumanCount(size_t i_human_count)
  {
  m_human_count = i_human_count;
  }

double GameController::GetHappiness() const
  {
  double happiness = 1.0 - std::exp(-GameConstants::TREE_EXP_COEF * m_tree_count);
  happiness += 1.0 - std::exp(-GameConstants::WATER_EXP_COEF * m_water_count);
  happiness += 1.0 - std::exp(-GameConstants::FERRIS_WHEEL_EXP_COEF * m_ferris_wheel_count);
  happiness += 1.0 - std::exp(-GameConstants::BALLOONS_SHOP_EXP_COEF * m_balloons_shop_count);
  happiness += 1.0 - std::exp(-GameConstants::BENCH_EXP_COEF * m_bench_count);
  happiness += m_landscape_beauty;
  return happiness / GameConstants::FACILITIES_COUNT;
  }

bool GameController::CanPlaceTree() const 
  {
  return m_money >= GameConstants::TREE_COST;
  }

bool GameController::CanCreateWalkway() const 
  {
  return m_money >= GameConstants::WALKWAY_COST;
  }

bool GameController::CanCreateGrass() const 
  {
  return m_money >= GameConstants::GRASS_COST;
  }

bool GameController::CanCreateWater() const 
  {
  return m_money >= GameConstants::WATER_COST;
  }

bool GameController::CanPlaceBalloonsShop() const 
  {
  return m_money >= GameConstants::BALLOONS_SHOP_COST;
  }

bool GameController::CanPlaceBench() const
  {
  return m_money >= GameConstants::BENCH_COST;
  }

void GameController::TreePlaced(bool i_pay_money)
  {
  if(i_pay_money)
    {
    m_money -= GameConstants::TREE_COST;
    _GenerateAllEvents();
    }
  ++m_tree_count;
  }

void GameController::BalloonsShopPlaced(bool i_pay_money)
  {
  if(i_pay_money)
    {
    m_money -= GameConstants::BALLOONS_SHOP_COST;
    _GenerateAllEvents();
    }
  ++m_balloons_shop_count;
  }

void GameController::FerrisWheelPlaced(bool i_pay_money)
  {
  if(i_pay_money)
    {
    m_money -= GameConstants::FERRIS_WHEEL_COST;
    _GenerateAllEvents();
    }
  ++m_ferris_wheel_count;
  }

void GameController::BenchPlaced(bool i_pay_money /* = true */)
  {
  if (i_pay_money)
    {
    m_money -= GameConstants::BENCH_COST;
    _GenerateAllEvents();
    }
  ++m_bench_count;
  }

void GameController::WalkwayCreated(bool i_pay_money)
  {
  if(i_pay_money)
    {
    m_money -= GameConstants::WALKWAY_COST;
    _GenerateAllEvents();
    }
  ++m_walkway_count;
  }

void GameController::GrassAdded(bool i_walkway_deleted, bool i_pay_money)
  {
  if(i_walkway_deleted)
    {
m_money -= GameConstants::GRASS_COST;
    WalkwayDeleted();
    }
  }

void GameController::WaterCreated(bool i_pay_money)
  {
  if(i_pay_money)
    {
    m_money -= GameConstants::WATER_COST;
    _GenerateAllEvents();
    }
  ++m_water_count;
  }

void GameController::LandscapeChanged(const Ground& i_ground)
  {
  m_landscape_beauty = _CalculateLandscapeBeauty(i_ground);

  _GenerateAllEvents();
  }

void GameController::SubscribeOnValuesChangedEvent(Observer i_callback) const
  {
  m_values_changed_event.AddSubscriber(i_callback);
  }

void GameController::SubscribeOnHumanCountChangedEvent(Observer i_callback) const
  {
  m_human_count_changed_event.AddSubscriber(i_callback);
  }

void GameController::_GenerateAllEvents() const
  {
  m_human_count_changed_event.Notify(EventArguments(std::to_string(_RecalculateHumanCount())));
  m_values_changed_event.Notify(EventArguments(""));
  }

size_t GameController::_RecalculateHumanCount() const
  {
  size_t human_count_by_happiness = GetHumanCountByHappiness(GetHappiness());
  size_t max_human_count = std::min(m_walkway_count / GameConstants::WALKWAYS_PER_HUMAN, human_count_by_happiness);
  return max_human_count;
   }

double GameController::_CalculateLandscapeBeauty(const Ground& i_ground) const
  {
  double mean = 0;
  int cnt = 0;
  for (size_t i = 0; i < i_ground.GetHeightMap().GetSizeX(); ++i)
    {
    for (size_t j = 0; j < i_ground.GetHeightMap().GetSizeY(); ++j)
      {
      mean += i_ground.GetHeightForXY(TVector2(i * GroundConstants::CELL_SIZE, j * GroundConstants::CELL_SIZE));
      ++cnt;
      }
    }
  mean /= cnt;
  double variance = 0;
  for (size_t i = 0; i < i_ground.GetHeightMap().GetSizeX(); ++i)
    {
    for (size_t j = 0; j < i_ground.GetHeightMap().GetSizeY(); ++j)
      {
      double diff = (mean - i_ground.GetHeightForXY(TVector2(i * GroundConstants::CELL_SIZE, j * GroundConstants::CELL_SIZE)));
      variance += diff * diff;
      }
    }
  variance /= cnt;

  double result = 0.0;
  if (variance <= GameConstants::GROUND_MAX_VARIANCE)
    result = 1.0 - fabs(2 * variance - GameConstants::GROUND_MAX_VARIANCE) / GameConstants::GROUND_MAX_VARIANCE;
  return result;
  }

void GameController::Reset()
  {
  m_money = GameConstants::STARTING_MONEY;
  m_balloons_shop_count = 0;
  m_ferris_wheel_count = 0;
  m_water_count = 0;
  m_tree_count = 0;
  m_walkway_count = 0;
  m_landscape_beauty = 0;
  m_bench_count = 0;
  m_human_count = 0;
  _GenerateAllEvents();
  }

void GameController::WalkwayDeleted()
  {
  m_money += GameConstants::WALKWAY_REFUND;
  --m_walkway_count;
  _GenerateAllEvents();
  }

void GameController::ObjectDestroyed(WorldObject* ip_object)
  {
  Tree* p_tree = dynamic_cast<Tree*>(ip_object);
  if (p_tree != nullptr)
    {
    --m_tree_count;
    m_money += GameConstants::TREE_REFUND;
    _GenerateAllEvents();
    return;
    }

  FerrisWheel* p_ferris_wheel = dynamic_cast<FerrisWheel*>(ip_object);
  if (p_ferris_wheel != nullptr)
    {
    --m_ferris_wheel_count;
    m_money += GameConstants::FERRIS_WHEEL_REFUND;
    _GenerateAllEvents();
    return;
    }

  BalloonsShop* p_balloons_shop = dynamic_cast<BalloonsShop*>(ip_object);
  if (p_balloons_shop != nullptr)
    {
    --m_balloons_shop_count;
    m_money += GameConstants::BALLOONS_SHOP_REFUND;
    _GenerateAllEvents();
    return;
    }

  if (dynamic_cast<Bench*>(ip_object))
    {
    --m_bench_count;
    m_money += GameConstants::BENCH_REFUND;
    _GenerateAllEvents();
    return;
    }
  }

void GameController::WaterRemoved()
  {
  m_money += GameConstants::WATER_REFUND;
  --m_water_count;
  _GenerateAllEvents();
  }

void GameController::MakeProfit()
  {
  int humans_for_ballons_shops = std::min(m_human_count, m_balloons_shop_count * GameConstants::BALLOONS_SHOP_HUMAN_CAPACITY);
  int humans_for_ferris_wheels = std::min(m_human_count, m_ferris_wheel_count  * GameConstants::FERRIS_WHEEL_HUMAN_CAPACITY);

  int balloons_shops_profit = humans_for_ballons_shops * GameConstants::BALLOONS_SHOP_PROFIT_PER_HUMAN;
  int ferris_wheels_profit  = humans_for_ferris_wheels * GameConstants::FERRIS_WHEEL_PROFIT_PER_HUMAN;

  int balloons_shops_service_cost = m_balloons_shop_count * GameConstants::BALLOONS_SHOP_SERVICE_COST;
  int ferris_wheels_service_cost  = m_ferris_wheel_count  * GameConstants::FERRIS_WHEEL_SERVICE_COST;

  int profit = balloons_shops_profit + ferris_wheels_profit;
  int service_cost = balloons_shops_service_cost + ferris_wheels_service_cost;
  
  m_money += profit;
  m_money -= service_cost;

  m_values_changed_event.Notify(EventArguments(""));
  }